import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

// 观察者接口
interface FileChangeObserver {
    void onFileChanged(String filePath, String changeDetails);
}

// 可观察对象接口
interface FileChangeSubject {
    void registerObserver(FileChangeObserver observer);
    void removeObserver(FileChangeObserver observer);
    void notifyObservers(String filePath, String changeDetails);
}

// 日志记录器观察者
class LogFileObserver implements FileChangeObserver {
    private final String logFilePath;

    public LogFileObserver(String logFilePath) {
        this.logFilePath = logFilePath;
    }

    @Override
    public void onFileChanged(String filePath, String changeDetails) {
        String logMessage = String.format("[%s] 文件 %s 发生变化: %s%n",
                new Date(), filePath, changeDetails);
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFilePath, true))) {
            writer.write(logMessage);
        } catch (IOException e) {
            System.err.println("日志记录错误: " + e.getMessage());
        }
        System.out.println("日志记录: " + logMessage.trim());
    }
}

// 文件监控器（可观察对象）
class FileMonitor implements FileChangeSubject {
    private final List<FileChangeObserver> observers = new ArrayList<>();
    private final String filePath;
    private String lastHash;

    public FileMonitor(String filePath) throws IOException {
        this.filePath = filePath;
        this.lastHash = calculateFileHash();
    }

    @Override
    public void registerObserver(FileChangeObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(FileChangeObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String filePath, String changeDetails) {
        for (FileChangeObserver observer : observers) {
            observer.onFileChanged(filePath, changeDetails);
        }
    }

    // 计算文件哈希值（MD5）以检测内容变化
    private String calculateFileHash() throws IOException {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            try (InputStream is = Files.newInputStream(Paths.get(filePath))) {
                byte[] buffer = new byte[8192];
                int read;
                while ((read = is.read(buffer)) != -1) {
                    digest.update(buffer, 0, read);
                }
            }
            byte[] hashBytes = digest.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new IOException("无法计算文件哈希: " + e.getMessage());
        }
    }

    // 启动文件监控
    public void startMonitoring() {
        try {
            WatchService watchService = FileSystems.getDefault().newWatchService();
            Path path = Paths.get(filePath).getParent();
            path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

            System.out.println("开始监控文件: " + filePath);
            while (true) {
                WatchKey key;
                try {
                    key = watchService.take();
                } catch (InterruptedException e) {
                    System.err.println("监控中断: " + e.getMessage());
                    break;
                }

                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();
                    if (kind == StandardWatchEventKinds.OVERFLOW) {
                        continue;
                    }

                    WatchEvent<Path> ev = (WatchEvent<Path>) event;
                    Path fileName = ev.context();
                    if (fileName.toString().equals(Paths.get(filePath).getFileName().toString())) {
                        String currentHash = calculateFileHash();
                        if (!currentHash.equals(lastHash)) {
                            String changeDetails = "文件内容已更改";
                            notifyObservers(filePath, changeDetails);
                            lastHash = currentHash;
                        }
                    }
                }

                boolean valid = key.reset();
                if (!valid) {
                    System.err.println("监控键无效，停止监控");
                    break;
                }
            }

            watchService.close();
        } catch (IOException e) {
            System.err.println("监控启动失败: " + e.getMessage());
        }
    }
}

public class FileMonitorSystem {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入要监控的文件路径: ");
        String filePath = scanner.nextLine();
        System.out.print("请输入日志文件路径: ");
        String logFilePath = scanner.nextLine();

        // 检查文件是否存在
        File file = new File(filePath);
        if (!file.exists()) {
            System.out.println("监控文件不存在！");
            scanner.close();
            return;
        }

        try {
            // 创建文件监控器
            FileMonitor monitor = new FileMonitor(filePath);

            // 注册观察者
            FileChangeObserver logObserver = new LogFileObserver(logFilePath);
            monitor.registerObserver(logObserver);

            // 启动监控（运行在单独线程中）
            new Thread(monitor::startMonitoring).start();

            // 提示用户修改文件
            System.out.println("请修改文件 " + filePath + " 的内容，按 Ctrl+C 停止监控");

            // 保持主线程运行，直到用户手动终止
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (InterruptedException e) {
                System.err.println("主线程中断: " + e.getMessage());
            }

        } catch (IOException e) {
            System.err.println("初始化监控失败: " + e.getMessage());
        }

        scanner.close();

        // 讨论与问答
        // 1. 观察者模式如何简化文件监控系统的实现
        //   解耦：监控器（Subject）和观察者（Observer）通过接口交互，降低耦合。
        //   动态注册：支持运行时添加或移除观察者，灵活性高。
        //   统一通知：通过notifyObservers方法统一通知所有观察者，简化事件分发。
        //   可复用：观察者逻辑（如日志记录）可独立开发和复用。

        // 2. 如何扩展系统以支持更多类型的观察者
        //   新增观察者：实现FileChangeObserver接口，如DatabaseObserver、WebhookObserver。
        //   参数化配置：通过构造参数支持不同配置（如日志路径、数据库连接）。
        //   异步通知：使用线程池（如ExecutorService）异步调用观察者方法。
        //   事件过滤：扩展接口添加事件类型（如创建、删除），支持更细粒度通知。

        // 3. 处理大量文件变化的性能问题
        //   批量处理：累积变化事件，定期批量通知观察者，减少频繁调用。
        //   异步处理：使用线程池异步执行观察者逻辑，避免阻塞监控线程。
        //   缓存优化：使用文件哈希（如MD5）快速比较变化，减少文件读取。
        //   限流：限制通知频率（如每秒最多通知一次）。
        //   分布式监控：对于多文件或多目录，使用分布式框架（如Apache Kafka）分发事件。
    }
}